﻿#region Includes
using System;
using System.IO;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using GingerGL;
using GingerGL.Math;
using GingerGL.Graphics;
#endregion

namespace GingerGL.Graphics.Models.BSP
{
    //------------------------------------------------------------//
    /// <summary>
    /// every patch (curved surface) is split into biquadratic (3x3) patches
    /// </summary>
    [Serializable]
    public struct BiquadraticPatch
    {
        //--------------------------------------------------------//
        #region Variables
        public int Tesselation;
        public GeometryBuffer PathBuffer;
        public Vertex[] ControlPoints;
        public int[] TrianglesPerRow;
        public int[] RowIndexPointers;
        #endregion
        //--------------------------------------------------------//
        #region Methods
        /// <summary>
        /// Draws Biquadratic Patch
        /// </summary>
        /// <param name="buffer">Buffer Data</param>
        public void Draw(Texture texture, Texture lightmap)
        {
            //load buffers if this is the first time to run
            if (!PathBuffer.IsCommited) throw new InvalidDataException("Buffers are corrupt.");
            #region Setup Buffer
            //enable arrays
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.TextureCoordArray);
            //set vertex buffer
            GL.BindBuffer(BufferTarget.ArrayBuffer, this.PathBuffer.VertexBuffer.VertexBufferId);
            #endregion
            //----------------------------------------------------//
            #region Vertex
            // Set the Pointer to the current bound array describing how the data ia stored
            GL.VertexPointer(3, VertexPointerType.Float, Vertex.SizeInBytes, Vertex.Offset(VertexOffset.Position));
            #endregion
            //----------------------------------------------------//
            #region Elements
            // Element Array Buffer
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, PathBuffer.IndexBuffer.IndexBufferId);  // Bind to the Array Buffer ID
            #endregion
            //----------------------------------------------------//
            #region Texture 1
            //Unit 1 - Lightmaps
            //setup blending
            GL.Disable(EnableCap.Blend);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //only bind a lightmap if one exists
            lightmap.Apply();
            //unit 1 - lightmap
            GL.TexCoordPointer(                                                     //create point for this texture
                2, TexCoordPointerType.Float, Vertex.SizeInBytes, Vertex.Offset(VertexOffset.Texture1));
            //draw face
            for (int row = 0; row < this.Tesselation; ++row)
            {
                GL.DrawElements(
                    BeginMode.TriangleStrip,
                    2 * (this.Tesselation + 1),
                    DrawElementsType.UnsignedInt,
                    sizeof(int) * row * 2 * (this.Tesselation + 1));
            }
            #endregion
            //----------------------------------------------------//
            #region Texture 0
            //set texture
            //GL.ClientActiveTexture(TextureUnit.Texture0);
            //unit 0 - decal texture
            texture.Apply();
            GL.Enable(EnableCap.Blend);
            GL.BlendFunc(BlendingFactorSrc.DstColor, BlendingFactorDest.SrcColor);
            GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Replace);
            //unit 0
            GL.TexCoordPointer(                                                     //create point for this texture
                2, TexCoordPointerType.Float, Vertex.SizeInBytes, Vertex.Offset(VertexOffset.Texture0));
            #endregion
            //----------------------------------------------------//
            #region Draw Elements
            // Draw the elements in the element array buffer
            // Draws up items in the Color, Vertex, TexCoordinate, and Normal Buffers using indices in the ElementArrayBuffer
            for (int row = 0; row < this.Tesselation; ++row)
            {
                GL.DrawElements(
                    BeginMode.TriangleStrip, 
                    2 * (this.Tesselation + 1), 
                    DrawElementsType.UnsignedInt,
                    sizeof(int) * row * 2 * (this.Tesselation + 1));
            }
            #endregion
            //----------------------------------------------------//
        }
        /// <summary>
        /// Recalculates Tesselation
        /// </summary>
        /// <param name="newTesselation">Resolution</param>
        /// <returns>TRUE when completed successfully</returns>
        public bool Tesselate(int newTesselation)
        {
            this.Tesselation = newTesselation;

            Vertex[] verts = new Vertex[(this.Tesselation + 1) * (this.Tesselation + 1)];
            float px, py;
            Vertex[] temp = new Vertex[3];

            for (int v = 0; v <= Tesselation; ++v)
            {
                px = (float)v / Tesselation;

                verts[v] =  this.ControlPoints[0] * ((1.0f - px) * (1.0f - px)) +
                            this.ControlPoints[3] * ((1.0f - px) * px * 2) +
                            this.ControlPoints[6] * (px * px);
            }

            for (int u = 1; u <= Tesselation; ++u)
            {
                py = (float)u / Tesselation;

                temp[0] = this.ControlPoints[0] * ((1.0f - py) * (1.0f - py)) +
                          this.ControlPoints[1] * ((1.0f - py) * py * 2) +
                          this.ControlPoints[2] * (py * py);

                temp[1] = this.ControlPoints[3] * ((1.0f - py) * (1.0f - py)) +
                          this.ControlPoints[4] * ((1.0f - py) * py * 2) +
                          this.ControlPoints[5] * (py * py);

                temp[2] = this.ControlPoints[6] * ((1.0f - py) * (1.0f - py)) +
                          this.ControlPoints[7] * ((1.0f - py) * py * 2) +
                          this.ControlPoints[8] * (py * py);

                for (int v = 0; v <= this.Tesselation; ++v)
                {
                    px = (float)v / this.Tesselation;
                    verts[u * (this.Tesselation + 1) + v] = temp[0] * ((1.0f - px) * (1.0f - px)) +
                                                            temp[1] * ((1.0f - px) * px * 2) +
                                                            temp[2] * (px * px);
                }
            }

            //Create indices
            int[] triangles = new int[this.Tesselation * (this.Tesselation + 1) * 2];

            for (int row = 0; row < this.Tesselation; ++row)
            {
                for (int point = 0; point <= this.Tesselation; ++point)
                {
                    //calculate indices
                    //reverse them to reverse winding
                    triangles[(row * (this.Tesselation + 1) + point) * 2 + 1] = row * (this.Tesselation + 1) + point;
                    triangles[(row * (this.Tesselation + 1) + point) * 2] = (row + 1) * (this.Tesselation + 1) + point;
                }
            }


            //Fill in the arrays for multi_draw_arrays
            TrianglesPerRow = new int[this.Tesselation];
            RowIndexPointers = new int[this.Tesselation];

            for (int row = 0; row < this.Tesselation; ++row)
            {
                TrianglesPerRow[row] = 2 * (this.Tesselation + 1);
                RowIndexPointers[row] = triangles[row * 2 * (this.Tesselation + 1)];
            }
            this.PathBuffer = new GeometryBuffer(new VertexBuffer(verts), new IndexBuffer(triangles));
            this.PathBuffer.Clear();
            return true;
        }
        #endregion
        //--------------------------------------------------------//
    }
    //------------------------------------------------------------//
}
